﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Rosetta.Composition
{
    public sealed class TemplateDepthCalculator : ITemplateVisitor
    {
        // TODO: Detect recursive calls (parent with higher depth)

        #region Fields

        private bool hasInputs;
        private bool hasOutputs;

        private int converterDepth;

        #endregion

        #region Constructors

        private TemplateDepthCalculator()
        {
        }

        #endregion

        #region Private Methods

        void ITemplateVisitor.Visit(InputterNode node)
        {
            hasInputs = true;

            int lastDepth = converterDepth;
            converterDepth = 0;
            VisitChildren(node);
            converterDepth = Math.Max(lastDepth, converterDepth);
        }

        void ITemplateVisitor.Visit(ConverterNode node)
        {
            VisitChildren(node);
        }

        void ITemplateVisitor.Visit(OutputterNode node)
        {
        }

        private void VisitChildren(Node node)
        {
            var children = node.Children.Select(c => c.EndPoint).ToArray();
            if (children.Length > 0)
            {
                if (children.Any(n => n.CanHaveChildren))
                {
                    converterDepth++;
                }
                if (children.Any(n => !n.CanHaveChildren))
                {
                    hasOutputs = true;
                }
                int lastDepth = converterDepth;
                foreach (var childNode in children)
                {
                    converterDepth = lastDepth;
                    childNode.Accept(this);
                    converterDepth = Math.Max(lastDepth, converterDepth);
                }
            }
        }

        #endregion

        #region Class Methods

        public static int GetDepth([NotNull] Template template)
        {
            if (template == null)
            {
                throw new ArgumentNullException("template");
            }

            var visitor = new TemplateDepthCalculator();
            template.Accept(visitor);

            int depth = 0;
            if (visitor.hasInputs)
            {
                depth++;
                depth += visitor.converterDepth;
                if (visitor.hasOutputs)
                {
                    depth++;
                }
            }

            return depth;
        }

        public static int GetDepth([NotNull] Node node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }

            if (node is InputterNode)
            {
                return 1;
            }
            if (node is OutputterNode)
            {
                return GetDepth(node.Template);
            }

            return node.Parents.Max(c => GetDepth(c.BeginPoint)) + 1;
        }

        public static SortedList<int, List<Node>> GetGraph([NotNull] Template template)
        {
            if (template == null)
            {
                throw new ArgumentNullException("template");
            }

            var result = new SortedList<int, List<Node>>();

            // Check each node
            foreach (var node in template.Nodes)
            {
                int depth = GetDepth(node);
                List<Node> nodes;
                if (!result.TryGetValue(depth, out nodes))
                {
                    result.Add(depth, nodes = new List<Node>());
                }
                nodes.Add(node);
            }

            return result;
        }

        #endregion
    }
}